package state

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/google/uuid"
)

// redisManager implements Manager interface with Redis storage
type redisManager struct {
	client *redis.Client
	prefix string
}

// NewRedisManager creates a new Redis-based state manager
func NewRedisManager(client *redis.Client, prefix string) Manager {
	return &redisManager{
		client: client,
		prefix: prefix,
	}
}

// Get retrieves a state by ID
func (m *redisManager) Get(ctx context.Context, id string) (*State, error) {
	key := m.getKey(id)
	data, err := m.client.Get(ctx, key).Bytes()
	if err != nil {
		if err == redis.Nil {
			return nil, fmt.Errorf("state not found: %s", id)
		}
		return nil, fmt.Errorf("failed to get state: %w", err)
	}

	var state State
	if err := json.Unmarshal(data, &state); err != nil {
		return nil, fmt.Errorf("failed to unmarshal state: %w", err)
	}
	return &state, nil
}

// Save saves a state
func (m *redisManager) Save(ctx context.Context, state *State) error {
	if state.ID == "" {
		state.ID = uuid.NewString()
	}
	state.CreatedAt = time.Now()
	state.UpdatedAt = state.CreatedAt
	state.Version = 1

	data, err := json.Marshal(state)
	if err != nil {
		return fmt.Errorf("failed to marshal state: %w", err)
	}

	key := m.getKey(state.ID)
	if err := m.client.Set(ctx, key, data, 0).Err(); err != nil {
		return fmt.Errorf("failed to save state: %w", err)
	}

	// Add to agent index
	indexKey := m.getAgentIndexKey(state.AgentID)
	if err := m.client.SAdd(ctx, indexKey, state.ID).Err(); err != nil {
		return fmt.Errorf("failed to update agent index: %w", err)
	}

	// Publish state change
	if err := m.publish(ctx, state); err != nil {
		return fmt.Errorf("failed to publish state change: %w", err)
	}

	return nil
}

// Update updates a state
func (m *redisManager) Update(ctx context.Context, state *State) error {
	key := m.getKey(state.ID)
	exists, err := m.client.Exists(ctx, key).Result()
	if err != nil {
		return fmt.Errorf("failed to check state existence: %w", err)
	}
	if exists == 0 {
		return fmt.Errorf("state not found: %s", state.ID)
	}

	// Get current state for version check
	current, err := m.Get(ctx, state.ID)
	if err != nil {
		return err
	}

	state.CreatedAt = current.CreatedAt
	state.UpdatedAt = time.Now()
	state.Version = current.Version + 1

	data, err := json.Marshal(state)
	if err != nil {
		return fmt.Errorf("failed to marshal state: %w", err)
	}

	if err := m.client.Set(ctx, key, data, 0).Err(); err != nil {
		return fmt.Errorf("failed to update state: %w", err)
	}

	// Publish state change
	if err := m.publish(ctx, state); err != nil {
		return fmt.Errorf("failed to publish state change: %w", err)
	}

	return nil
}

// Delete deletes a state
func (m *redisManager) Delete(ctx context.Context, id string) error {
	// Get state first for agent ID
	state, err := m.Get(ctx, id)
	if err != nil {
		return err
	}

	key := m.getKey(id)
	if err := m.client.Del(ctx, key).Err(); err != nil {
		return fmt.Errorf("failed to delete state: %w", err)
	}

	// Remove from agent index
	indexKey := m.getAgentIndexKey(state.AgentID)
	if err := m.client.SRem(ctx, indexKey, id).Err(); err != nil {
		return fmt.Errorf("failed to update agent index: %w", err)
	}

	// Publish deletion event
	deleteState := &State{
		ID:      id,
		AgentID: state.AgentID,
		Type:    string(EventTypeDeleted),
	}
	if err := m.publish(ctx, deleteState); err != nil {
		return fmt.Errorf("failed to publish state deletion: %w", err)
	}

	return nil
}

// List lists states by agent ID
func (m *redisManager) List(ctx context.Context, agentID string) ([]*State, error) {
	indexKey := m.getAgentIndexKey(agentID)
	ids, err := m.client.SMembers(ctx, indexKey).Result()
	if err != nil {
		return nil, fmt.Errorf("failed to get agent states: %w", err)
	}

	var states []*State
	for _, id := range ids {
		state, err := m.Get(ctx, id)
		if err != nil {
			continue // Skip invalid states
		}
		states = append(states, state)
	}
	return states, nil
}

// Watch watches for state changes
func (m *redisManager) Watch(ctx context.Context, agentID string) (<-chan *State, error) {
	ch := make(chan *State, 100)
	pubsub := m.client.Subscribe(ctx, m.getChannelName(agentID))

	go func() {
		defer pubsub.Close()
		defer close(ch)

		for {
			select {
			case <-ctx.Done():
				return
			case msg := <-pubsub.Channel():
				var state State
				if err := json.Unmarshal([]byte(msg.Payload), &state); err != nil {
					continue
				}
				select {
				case ch <- &state:
				default:
					// Skip if channel is full
				}
			}
		}
	}()

	return ch, nil
}

// Helper methods

func (m *redisManager) getKey(id string) string {
	return fmt.Sprintf("%s:state:%s", m.prefix, id)
}

func (m *redisManager) getAgentIndexKey(agentID string) string {
	return fmt.Sprintf("%s:agent:%s:states", m.prefix, agentID)
}

func (m *redisManager) getChannelName(agentID string) string {
	return fmt.Sprintf("%s:agent:%s:changes", m.prefix, agentID)
}

func (m *redisManager) publish(ctx context.Context, state *State) error {
	data, err := json.Marshal(state)
	if err != nil {
		return err
	}
	return m.client.Publish(ctx, m.getChannelName(state.AgentID), data).Err()
}
